﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace GameLibrary
{
    public abstract class GamePanel : Panel
    {
        #region Constants
        private const int DEFAULT_FPS = 60;
        #endregion

        #region Private Members
        private Timer m_updateTimer = new Timer();
        private int m_fps = 0;
        private DateTime m_lastEngineCycle;
        private DateTime m_currentEngineCycle;
        private Mouse m_mouse = null;
        private SizeF m_initialSize;
        #endregion

        #region Public Properties
        public int FramesPerSecond
        {
            get { return m_fps; }
            set
            {
                m_fps = value;

                m_updateTimer.Interval = (int)Math.Round((1f / (float)m_fps * 1000f));
            }
        }

        public SizeF InitialGameSize
        {
            get { return m_initialSize; }
        }

        public Mouse Mouse
        {
            get { return m_mouse; }
        }
        #endregion

        #region Constructor(s)
        public GamePanel(int initialWidth, int initialHeight)
            : this(DEFAULT_FPS, initialWidth, initialHeight)
        {
        }

        public GamePanel(int fps, int gameWidth, int gameHeight)
            : base()
        {
            this.DoubleBuffered = true;
            this.FramesPerSecond = fps;

            m_initialSize = new SizeF(gameWidth, gameHeight);

            m_mouse = new Mouse(this);

            m_updateTimer.Tick += new EventHandler(HandleUpdateTick);
            m_updateTimer.Start();
            m_lastEngineCycle = DateTime.Now;
        }
        #endregion

        #region Override Methods
        protected override CreateParams CreateParams
        {
            get
            {
                // This stops the control from flickering when it draws
                CreateParams cp = base.CreateParams;

                // Only allow this on vista and higher as lower versions seem to not draw lines with this option.
                if (Environment.OSVersion.Version.Major >= 6)
                    cp.ExStyle |= 0x02000000; // (this is WS_EX_COMPOSITED)

                return cp;
            }
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);

            Cursor.Hide();
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);

            Cursor.Show();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            OnDraw(m_currentEngineCycle - m_lastEngineCycle, e.Graphics);
        }
        #endregion

        #region Static Methods
        public static Size CalculateWindowBounds(Size gameRectangle, FormBorderStyle borderStyle)
        {
            Size s = gameRectangle;

            switch (borderStyle)
            {
                case FormBorderStyle.Sizable:
                    s.Width += SystemInformation.FrameBorderSize.Width * 2;
                    s.Height += SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height * 2;
                    break;
                case FormBorderStyle.FixedDialog:
                    s.Width += SystemInformation.FixedFrameBorderSize.Width * 2;
                    s.Height += SystemInformation.CaptionHeight + SystemInformation.FixedFrameBorderSize.Height * 2;
                    break;
            }

            return s;
        }
        #endregion

        #region Protected Abstract Methods
        protected abstract void OnUpdate(TimeSpan elapsedTime);

        protected abstract void OnDraw(TimeSpan elapsedTime, Graphics g);
        #endregion

        #region Event Handlers
        private void HandleUpdateTick(object sender, EventArgs e)
        {
            m_currentEngineCycle = DateTime.Now;

            OnUpdate(m_currentEngineCycle - m_lastEngineCycle);

            this.Invalidate();

            m_lastEngineCycle = m_currentEngineCycle;
        }
        #endregion
    }
}
